package cc.shacocloud.mirage.utils.resource;

import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;


/**
 * 基于 {@link java.io.File} 和 {@link  java.nio.file.Path} 实现的  {@link Resource}
 */
public class FileSystemResource extends AbstractResource {
    
    @Getter
    private final String path;
    
    @Nullable
    private final File file;
    
    private final Path filePath;
    
    public FileSystemResource(@NotNull String path) {
        this.path = ResourceUtil.normalize(path);
        this.file = new File(this.path);
        this.filePath = this.file.toPath();
    }
    
    public FileSystemResource(@NotNull File file) {
        this.path = ResourceUtil.normalize(file.getAbsolutePath());
        this.file = file;
        this.filePath = file.toPath();
    }
    
    public FileSystemResource(@NotNull Path filePath) {
        this.path = ResourceUtil.normalize(filePath.toAbsolutePath().toString());
        this.file = null;
        this.filePath = filePath;
    }
    
    public FileSystemResource(@NotNull FileSystem fileSystem, @NotNull String path) {
        this.path = ResourceUtil.normalize(path);
        this.file = null;
        this.filePath = fileSystem.getPath(this.path).normalize();
    }
    
    /**
     * 文件是否存在
     *
     * @see java.io.File#exists()
     */
    @Override
    public boolean exists() {
        return (this.file != null ? this.file.exists() : Files.exists(this.filePath));
    }
    
    /**
     * 此实现检查基础文件是否标记为可读
     *
     * @see java.io.File#canRead()
     * @see java.io.File#isDirectory()
     */
    @Override
    public boolean isReadable() {
        return (this.file != null ? this.file.canRead() && !this.file.isDirectory() :
                Files.isReadable(this.filePath) && !Files.isDirectory(this.filePath));
    }
    
    /**
     * 获取文件流
     *
     * @see java.io.FileInputStream
     */
    @Override
    public InputStream getStream() throws IOException {
        try {
            return Files.newInputStream(this.filePath);
        } catch (NoSuchFileException ex) {
            throw new FileNotFoundException(ex.getMessage());
        }
    }
    
    /**
     * 此实现返回基础文件的 URL
     *
     * @see java.io.File#toURI()
     */
    @Override
    public URL getURL() throws IOException {
        return (this.file != null ? this.file.toURI().toURL() : this.filePath.toUri().toURL());
    }
    
    @Override
    public boolean isFile() {
        return true;
    }
    
    @Override
    public File getFile() {
        return (this.file != null ? this.file : this.filePath.toFile());
    }
    
    
    /**
     * 此实现返回基础文件路径长度
     */
    @Override
    public long contentLength() throws IOException {
        if (this.file != null) {
            long length = this.file.length();
            if (length == 0L && !this.file.exists()) {
                throw new FileNotFoundException(getDescription() + " 无法在文件系统中解析以检查其内容长度");
            }
            return length;
        } else {
            try {
                return Files.size(this.filePath);
            } catch (NoSuchFileException ex) {
                throw new FileNotFoundException(ex.getMessage());
            }
        }
    }
    
    /**
     * 此实现返回基础文件路径上次修改时间
     */
    @Override
    public long lastModified() throws IOException {
        if (this.file != null) {
            return super.lastModified();
        } else {
            try {
                return Files.getLastModifiedTime(this.filePath).toMillis();
            } catch (NoSuchFileException ex) {
                throw new FileNotFoundException(ex.getMessage());
            }
        }
    }
    
    /**
     * 此实现返回包含文件绝对路径的说明
     *
     * @see java.io.File#getAbsolutePath()
     */
    @Override
    public String getDescription() {
        return "file [" + (this.file != null ? this.file.getAbsolutePath() : this.filePath.toAbsolutePath()) + "]";
    }
    
    @Override
    public boolean equals(@Nullable Object other) {
        return (this == other || (other instanceof FileSystemResource &&
                this.path.equals(((FileSystemResource) other).path)));
    }
    
    @Override
    public int hashCode() {
        return this.path.hashCode();
    }
    
}
